﻿using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System;

namespace WindowsFormsApp_TestLibuECC
{
    class LibuECCHelper
    {
        [StructLayout(LayoutKind.Sequential)]
        private struct LibuECCBuffer32
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            public byte[] buffer;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct LibuECCBuffer64
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
            public byte[] buffer;
        }

        private const int Dll_return_success = 0;
        #region DLL_IMPORT   
        [DllImport("LibuECC.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "get_version")]
        private static extern int Dll_get_version();


        [DllImport("LibuECC.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "LibuECC_generate_key_pair")]
        private static extern int Dll_generate_key_pair(ref LibuECCBuffer32 p_private_key,
            ref LibuECCBuffer64 p_public_key);

        [DllImport("LibuECC.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "LibuECC_get_publickey")]
        private static extern int Dll_get_publickey(ref LibuECCBuffer32 p_private_key,
            ref LibuECCBuffer64 p_public_key);

        [DllImport("LibuECC.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "LibuECC_get_shared_secret")]
        private static extern int Dll_get_shared_secret(ref LibuECCBuffer32 p_private_key,
            ref LibuECCBuffer64 p_public_key,
            ref LibuECCBuffer32 p_shared_secret);

        [DllImport("LibuECC.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "LibuECC_is_on_Curve")]
        private static extern int Dll_is_on_Curve(ref LibuECCBuffer64 p_public_key);

        [DllImport("LibuECC.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "LibuECC_Sign")]
        private static extern int Dll_Sign(ref LibuECCBuffer32 p_private_key,
            ref LibuECCBuffer32 p_sha256,
            ref LibuECCBuffer64 p_signed_data);

        [DllImport("LibuECC.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "LibuECC_verify")]
        private static extern int Dll_verify(ref LibuECCBuffer64 p_public_key,
            ref LibuECCBuffer32 p_sha256,
            ref LibuECCBuffer64 p_signed_data,
            ref bool p_isValid);
        #endregion

        private LibuECCBuffer32 p_private = new LibuECCBuffer32();
        private LibuECCBuffer32 p_shared = new LibuECCBuffer32();
        private LibuECCBuffer64 p_public = new LibuECCBuffer64();

        #region private functions
        private string BytesToBase64String(byte[] value)
        {
            if (value == null)
            {
                return "";
            }
            return Convert.ToBase64String(value);
        }
        private byte[] ComputeSHA256(string value)
        {
            if (value == null)
            {
                return null;
            }
            SHA256 m_sha256 = SHA256Managed.Create();
            byte[] value_bytes = System.Text.Encoding.UTF8.GetBytes(value);

            byte[] sha256_value = m_sha256.ComputeHash(value_bytes);
            return sha256_value;
        }

        private byte[] Base64StringToBytes(string value)
        {
            if (value == null)
            {
                return null;
            }
            byte[] ret;

            try
            {
                ret = Convert.FromBase64String(value);
            }
            catch (FormatException)
            {
                return null;
            }
            return ret;
        }
        #endregion


        public byte[] PrivateKey
        {
            set
            {
                if (value == null)
                {
                    return;
                }
                if (value.Length != 32)
                {
                    return;
                }
                p_private.buffer = value;
            }
        }

        public byte[] SharedSecret
        {
            set
            {
                if(value == null)
                {
                    return;
                }
                if(value.Length != 32)
                {
                    return;
                }
                p_shared.buffer = value;
            }
            get
            {
                return p_shared.buffer;
            }
        }

        public byte[] PublicKey
        {
            get
            {
                return p_public.buffer;
            }
            set
            {
                if (value == null)
                {
                    return;
                }
                if (value.Length != 64)
                {
                    return;
                }
                p_public.buffer = value;
            }
        }

        public static int Get_version()
        {
            return Dll_get_version();
        }

        public int ReGenerateKeyPair()
        {
            int ret = Dll_generate_key_pair(ref p_private, ref p_public);
            if (ret == Dll_return_success)
            {
                ret = 0;
            }
            else
            {
                ret = -1;
            }

            return ret;
        }

        public int ReFreshPublicKey()
        {
            int ret = 0;
            ret = Dll_get_publickey(ref p_private, ref p_public);
            if (ret == Dll_return_success)
            {
                ret = 0;
            }
            else
            {
                ret = -1;
            }
            return ret;
        }

        public string Sign(string value)
        {
            LibuECCBuffer32 p_sha256 = new LibuECCBuffer32()
            {
                buffer = ComputeSHA256(value)
            };
            LibuECCBuffer64 p_signed_data = new LibuECCBuffer64();
            int ret;
            ret = Dll_Sign(ref p_private, ref p_sha256, ref p_signed_data);
            if (ret == Dll_return_success)
            {
                return BytesToBase64String(p_signed_data.buffer);
            }
            else
            {
                return "";
            }
        }

        public bool Verify(string value, string base64_signed_data)
        {
            LibuECCBuffer32 p_sha256 = new LibuECCBuffer32()
            {
                buffer = ComputeSHA256(value)
            };
            LibuECCBuffer64 p_signed_data = new LibuECCBuffer64()
            {
                buffer = Base64StringToBytes(base64_signed_data)
            };
            int ret;
            bool isValid = false;
            ret = Dll_verify(ref p_public, ref p_sha256, ref p_signed_data, ref isValid);
            if (ret == Dll_return_success)
            {
                return isValid;
            }
            else
            {
                return false;
            }
        }

        public byte[] GenSharedSecret(string remotePublicKeyBase64)
        {
            byte[] remotePublicKey = Base64StringToBytes(remotePublicKeyBase64);
            if(remotePublicKey == null)
            {
                return null;
            }

            if(remotePublicKey.Length != 64)
            {
                return null;
            }

            LibuECCBuffer64 remote_pub_key = new LibuECCBuffer64()
            {
                buffer = remotePublicKey,
            };


            int ret = Dll_get_shared_secret(ref p_private, 
                ref remote_pub_key, 
                ref p_shared);

            if (ret == Dll_return_success)
            {
                return SharedSecret;
            }
            else
            {
                return null;
            }
        }

        public void ModifyPrivateKeyFromBase64(string base64privateKey)
        {
            p_private.buffer = Base64StringToBytes(base64privateKey);
            ReFreshPublicKey();
        }

        public void ModifyPublicKeyFromBase64(string base64publicKey)
        {
            p_public.buffer = Base64StringToBytes(base64publicKey);
        }

        private string ReadOutPrivateKey()
        {
            return BytesToBase64String(p_private.buffer);
        }

        public string ReadOutPublicKey()
        {
            return BytesToBase64String(p_public.buffer);
        }

        public string ReadOutSharedSecret()
        {
            return BytesToBase64String(p_shared.buffer);
        }
    }
}
